package com.jxpanda.commons.encrypt;

import com.jxpanda.commons.toolkit.Base64Kit;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.security.spec.AlgorithmParameterSpec;

/**
 * 加解密算法
 *
 * @author Panda
 */
@Slf4j
@Getter
@Setter
public abstract class AbstractEncrypt {

    protected EncryptAlgorithm algorithm;

    protected Cipher encoder;

    protected Cipher decoder;


    public AbstractEncrypt(EncryptAlgorithm algorithm) {
        Security.addProvider(new BouncyCastleProvider());
        this.algorithm = algorithm;
    }

    public Cipher getEncoder() {
        if (encoder == null) {
            try {
                encoder = initEncoder(createCipher());
            } catch (Exception ex) {
                log.error("[INIT ENCODER ERROR]", ex);
            }
        }
        return encoder;
    }

    public Cipher getDecoder() {
        if (decoder == null) {
            try {
                decoder = initDecoder(createCipher());
            } catch (Exception ex) {
                log.error("[INIT DECODER ERROR]", ex);
            }
        }
        return decoder;
    }

    protected Cipher createCipher() {
        try {
            return Cipher.getInstance(algorithm.getValue());
        } catch (NoSuchAlgorithmException | NoSuchPaddingException ex) {
            log.error("[CREATE CIPHER ERROR]", ex);
        }
        return null;
    }

    /**
     * 初始化encoder
     * 因为对称加密和非对称加密用的key不同，所以只能交给子类实现了
     *
     * @param encoderCipher encoderCipher对象
     * @return 返回cipher对象 支持链式调用
     */
    abstract protected Cipher initEncoder(Cipher encoderCipher);

    /**
     * 初始化decoder
     * 因为对称加密和非对称加密用的key不同，所以只能交给子类实现了
     *
     * @param decoderCipher decoderCipher对象
     * @return 返回cipher对象 支持链式调用
     */
    abstract protected Cipher initDecoder(Cipher decoderCipher);

    public byte[] encrypt(byte[] data) {
        byte[] bytes = new byte[0];
        try {
            bytes = this.getEncoder().doFinal(data);
        } catch (IllegalBlockSizeException | BadPaddingException ex) {
            log.error("[ENCRYPT ERROR]", ex);
        }
        return bytes;
    }

    public byte[] decrypt(byte[] data) {
        byte[] bytes = new byte[0];
        try {
            bytes = this.getDecoder().doFinal(data);
        } catch (IllegalBlockSizeException | BadPaddingException ex) {
            log.error("[DECRYPT ERROR]", ex);
        }
        return bytes;
    }

    /**
     * String类型的重载，方便使用，估计用的多的也是这个
     */
    public String encrypt(String content) {
        return new String(encrypt(content.getBytes()));
    }

    public String decrypt(String content) {
        return new String(decrypt(content.getBytes()));
    }

    /**
     * 默认的把字符串转为Key类型的函数，子类可以重写
     */
    protected Key covert2Key(String key) {
        return covert2Key(key.getBytes());
    }

    /**
     * 默认的把byte数组转为Key类型的函数，子类可以重写
     */
    protected Key covert2Key(byte[] keyBytes) {
        return new SecretKeySpec(keyBytes, algorithm.getValue());
    }

    /**
     * 对称加密
     */
    @Getter
    @Setter
    @EqualsAndHashCode(callSuper = true)
    public static class Symmetric extends AbstractEncrypt {
        /**
         * 密钥Key
         */
        protected Key key;

        /**
         * 算法参数
         * 偏移值IV用的多一点
         */
        protected AlgorithmParameterSpec spec;

        public Symmetric(EncryptAlgorithm algorithm) {
            super(algorithm);
        }

        @Override
        protected Cipher initEncoder(Cipher encoderCipher) {
            try {
                if (spec != null) {
                    encoderCipher.init(Cipher.ENCRYPT_MODE, this.key, spec);
                } else {
                    encoderCipher.init(Cipher.ENCRYPT_MODE, this.key);
                }
            } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
                log.error("INIT ENCODER ERROR", e);
            }
            return encoderCipher;
        }

        @Override
        protected Cipher initDecoder(Cipher decoderCipher) {
            try {
                if (spec != null) {
                    decoderCipher.init(Cipher.DECRYPT_MODE, this.key, spec);
                } else {
                    decoderCipher.init(Cipher.DECRYPT_MODE, this.key);
                }
            } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
                log.error("INIT DECODER ERROR", e);
            }
            return decoderCipher;
        }

        public Symmetric(EncryptAlgorithm algorithm, Key key) {
            super(algorithm);
            this.key = key;
        }

        public Symmetric(EncryptAlgorithm algorithm, String key) {
            super(algorithm);
            this.key = covert2Key(key);
        }

        public Symmetric(EncryptAlgorithm algorithm, byte[] keyBytes) {
            super(algorithm);
            this.key = covert2Key(keyBytes);
        }

        /**
         * String类型的重载，方便使用，估计用的多的也是这个
         */
        @Override
        public String encrypt(String content) {
            return Base64Kit.encodeToString(encrypt(content.getBytes()));
        }

        @Override
        public String decrypt(String content) {
            return Base64Kit.decodeToString(decrypt(content.getBytes()));
        }

    }

    /**
     * 非对称加密
     */
    @Getter
    @Setter
    @EqualsAndHashCode(callSuper = true)
    public static class Asymmetric extends AbstractEncrypt {
        protected KeyPair keyPair;

        public Asymmetric(EncryptAlgorithm algorithm) {
            super(algorithm);
        }

        @Override
        protected Cipher initEncoder(Cipher encoderCipher) {
            try {
                encoderCipher.init(Cipher.ENCRYPT_MODE, this.getKeyPair().getPublic());
            } catch (InvalidKeyException ex) {
                log.error("[INIT ENCODER ERROR]", ex);
            }
            return encoderCipher;
        }

        @Override
        protected Cipher initDecoder(Cipher decoderCipher) {
            try {
                decoderCipher.init(Cipher.DECRYPT_MODE, this.getKeyPair().getPrivate());
            } catch (InvalidKeyException ex) {
                log.error("[INIT DECODER ERROR]", ex);
            }
            return decoderCipher;
        }

        public Asymmetric(EncryptAlgorithm algorithm, KeyPair keyPair) {
            super(algorithm);
        }

    }

}
